package com.tianji.promotion.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.discount.Discount;
import com.tianji.promotion.discount.DiscountStrategy;
import com.tianji.promotion.domain.dto.CouponDiscountDTO;
import com.tianji.promotion.domain.dto.OrderCourseDTO;
import com.tianji.promotion.domain.dto.UserCouponDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.tianji.promotion.utils.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author xhr
 * @since 2024-06-11
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserCouponMQServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {

    private final CouponMapper couponMapper;
    private final StringRedisTemplate redisTemplate;
    private final IExchangeCodeService exchangeCodeService;
    private final RedissonClient redissonClient;
    private final MyLockFactory myLockFactory;
    private final RabbitMqHelper rabbitMqHelper;
    private final ICouponScopeService couponScopeService;
    private final Executor caculateSolutionExecutor;

    //对优惠券加锁
    @MyLock(name = "lock:coupon:uid:#{id}")
    @Transactional
    public void receiveCoupon(Long id) {
        if (id==null){
            throw new BadRequestException("非法参数");
        }
        //1.查询优惠券
        Coupon coupon = queryCouponByCache(id);
        //2.是否发放
        LocalDateTime issueBeginTime = coupon.getIssueBeginTime();
        LocalDateTime issueEndTime = coupon.getIssueEndTime();
        LocalDateTime now = LocalDateTime.now();
//        if (coupon.getStatus()!=CouponStatus.ISSUING || now.isBefore(issueBeginTime) || now.isAfter(issueEndTime)){
//            throw new BadRequestException("该优惠券还未发放");
//        }
        if (now.isBefore(issueBeginTime) || now.isAfter(issueEndTime)){
            throw new BadRequestException("该优惠券已过期或未开始发放");
        }
        //3.库存是否充足
        if (coupon.getTotalNum()<=0){
            throw new BadRequestException("库存不足");
        }
        //拼接key
        String key = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + id;
        Long userId = UserContext.getUser();
        Long increment = redisTemplate.opsForHash().increment(key, userId.toString(), 1);
        if (increment>coupon.getUserLimit()){
            throw new BizIllegalException("超出限领数量");
        }
        UserCouponDTO dto = new UserCouponDTO();
        dto.setCouponId(id);
        dto.setUserId(userId);
        //扣减库存
        String couponkey = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;
        redisTemplate.opsForHash().increment(couponkey,"totalNum",-1);
        //向MQ发送消息
        rabbitMqHelper.send(
                MqConstants.Exchange.PROMOTION_EXCHANGE,
                MqConstants.Key.COUPON_RECEIVE,
                dto
                );
        //4.是否超出限领数量
/*        Integer count = this.lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getCouponId, coupon.getId())
                .count();
        if (count != null && count>=coupon.getUserLimit()){
            throw new BadRequestException("已达到领取上限");
        }
        //5.优惠券已发放数量+1
        couponMapper.incrIssueNum(coupon.getId());
        //6.生成用户券
        generateUserCouPon(userId,coupon);*/
        //通过Redission实现分布式锁
        /*String key = "lock:coupon:uid"+userId;
        RLock lock = redissonClient.getLock(key);
        try {
            boolean tryLock = lock.tryLock();//默认锁失效时间为30s 走看门狗机制
            if (!tryLock){
                throw new BizIllegalException("操作繁忙请稍后再试");
            }
            //获取当前实现类的代理对象
            IUserCouponService userCouponServiceProxy = (IUserCouponService)AopContext.currentProxy();
            userCouponServiceProxy.checkAndGenerateUserConPon(userId,coupon,null);
        }finally {
            lock.unlock();//释放锁
        }*/
        //获取当前实现类的代理对象
//        IUserCouponService userCouponServiceProxy = (IUserCouponService)AopContext.currentProxy();
//        userCouponServiceProxy.checkAndGenerateUserConPon(userId,coupon,null);
    }

    private Coupon queryCouponByCache(Long id) {
        //拼接key
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;
        //获取key对应的所有键值对
        Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
        if (map.isEmpty()){
            return null;
        }
        Coupon coupon = BeanUtils.mapToBean(map, Coupon.class,false, CopyOptions.create());
        return coupon;
    }

    @Override
    public void exchangeCoupon(String code) {
        //1.校验参数
        if (code==null){
            throw new BadRequestException("非法参数");
        }
        //2.解析兑换码 获取自增id
        long serialNum = CodeUtil.parseCode(code);
        //3.是否已经兑换
        boolean result = exchangeCodeService.updateExchangeCode(serialNum,true);
        if (result){
            throw new BizIllegalException("优惠券已兑换");
        }
        try {
            //3.2.否 查询兑换码
            ExchangeCode exchangeCode = exchangeCodeService.getById(serialNum);
            //4.兑换码是否存在
            if (exchangeCode==null){
                throw new BizIllegalException("优惠券不存在");
            }
            //5.是否过期
            LocalDateTime now = LocalDateTime.now();
            if(now.isAfter(exchangeCode.getExpiredTime())){
                throw new BizIllegalException("优惠券已过期");
            }
            Long userId = UserContext.getUser();
            Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
            if (coupon==null){
                throw new BizIllegalException("优惠券不存在");
            }
            checkAndGenerateUserConPon(userId,coupon,serialNum);
        }catch (Exception e){
            //重置兑换码的兑换状态
            exchangeCodeService.updateExchangeCode(serialNum,false);
            throw e;
        }
    }

    @Transactional
    public void checkAndGenerateUserConPon(Long userId, Coupon coupon, Long serialNum) {
            //4.是否超出限领数量
            Integer count = this.lambdaQuery()
                    .eq(UserCoupon::getUserId, userId)
                    .eq(UserCoupon::getCouponId, coupon.getId())
                    .count();
            if (count != null && count>=coupon.getUserLimit()){
                throw new BadRequestException("已达到领取上限");
            }
            //5.优惠券已发放数量+1
            int r = couponMapper.incrIssueNum(coupon.getId());
            if (r==0){
                throw new BizIllegalException("优惠券库存不足");
            }
        //6.生成用户券
            generateUserCouPon(userId,coupon);
            //7.更新兑换码的状态为已兑换
            if (serialNum!=null){
                exchangeCodeService.lambdaUpdate()
                        .set(ExchangeCode::getStatus,ExchangeCodeStatus.USED)
                        .set(ExchangeCode::getUserId,userId)
                        .eq(ExchangeCode::getId,serialNum)
                        .update();
            }
        }


    @Transactional
    public void checkAndGenerateUserConPonMq(UserCouponDTO dto) {
//        //4.是否超出限领数量
//        Integer count = this.lambdaQuery()
//                .eq(UserCoupon::getUserId, userId)
//                .eq(UserCoupon::getCouponId, coupon.getId())
//                .count();
//        if (count != null && count>=coupon.getUserLimit()){
//            throw new BadRequestException("已达到领取上限");
//        }
        Coupon coupon = couponMapper.selectById(dto.getCouponId());
        Long userId = dto.getUserId();
        if (coupon==null){
            //不能抛异常，会重试
            return;
        }
        //5.优惠券已发放数量+1
        int r = couponMapper.incrIssueNum(coupon.getId());
        if (r==0){
            return;
        }
        //6.生成用户券
        generateUserCouPon(userId,coupon);
    }

    @Override
    public List<CouponDiscountDTO> findDiscountSolution(List<OrderCourseDTO> orderCourses) {
        //1.查询当前用户可用的优惠券 coupon表 user_coupon表 条件 user_id,status = 1,
        List<Coupon> coupons = getBaseMapper().queryMyCoupon(UserContext.getUser());
        if (CollUtils.isEmpty(coupons)){
            return CollUtils.emptyList();
        }
        //2.初筛
        //2.1计算总的课程价格
        int totalPrice = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
        //2.2判断是否达到使用条件
        List<Coupon> availableCoupons = coupons.stream()
                .filter(coupon -> DiscountStrategy.getDiscount(coupon.getDiscountType())
                        .canUse(totalPrice, coupon)).collect(Collectors.toList());
        if (CollUtils.isEmpty(availableCoupons)){
            return CollUtils.emptyList();
        }
        //3.细筛 考虑优惠券限定范围后的所有排列组合
        Map<Coupon,List<OrderCourseDTO>> map = findAviailable(coupons,orderCourses);
        if (CollUtils.isEmpty(map)){
            return CollUtils.emptyList();
        }
        List<Coupon> availableCoupon = new ArrayList<>(map.keySet());
        //优惠券的所有排列组合
        List<List<Coupon>> solutions = PermuteUtil.permute(availableCoupon);
        for (Coupon coupon : availableCoupon) {
            solutions.add(List.of(coupon));
        }
        //4.计算每一种组合的优惠明细
//        List<CouponDiscountDTO> couponDiscountDTOS = new ArrayList<>();
//        for (List<Coupon> solution : solutions) {
//            CouponDiscountDTO dto = calculateSolutionDiscount(map,orderCourses,solution);
//            couponDiscountDTOS.add(dto);
//        }
        //5.多线程计算优惠明细(优化)
        List<CouponDiscountDTO> couponDiscountDTOS = Collections.synchronizedList(new ArrayList<>(solutions.size()));
        CountDownLatch latch = new CountDownLatch(solutions.size());
        for (List<Coupon> solution : solutions) {
            CompletableFuture.supplyAsync(new Supplier<CouponDiscountDTO>() {
                @Override
                public CouponDiscountDTO get() {
                    log.debug("线程:{},计算优惠方案",Thread.currentThread().getName());
                    CouponDiscountDTO dto = calculateSolutionDiscount(map,orderCourses,solution);
                    return dto;
                }
            }, caculateSolutionExecutor).thenAccept(new Consumer<CouponDiscountDTO>() {
                @Override
                public void accept(CouponDiscountDTO couponDiscountDTO) {
                    log.info("优惠券id:{},优惠规则:{},优惠金额:{}",couponDiscountDTO.getIds(),couponDiscountDTO.getRules(),couponDiscountDTO.getDiscountAmount());
                    couponDiscountDTOS.add(couponDiscountDTO);
                    latch.countDown();
                }
            });
        }
        try {
            latch.await(2, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.debug("多线程计算组合优惠明细报错了:{}",e);
        }
        //6.筛选最优解
        return findBestSolution(couponDiscountDTOS);
    }

    private List<CouponDiscountDTO> findBestSolution(List<CouponDiscountDTO> couponDiscountDTOS) {
        //1.创建两个map <券id组合,优惠方案>(相同优惠券,金额最大) <优惠金额,优惠方案>(相同金额，用券数最少)
        Map<String,CouponDiscountDTO> moreDiscountMap = new HashMap<>();
        Map<Integer,CouponDiscountDTO> lessCouponMap = new HashMap<>();
        for (CouponDiscountDTO dto : couponDiscountDTOS) {
            String ids = dto.getIds().stream().sorted(Comparator.comparing(Long::longValue)).map(String::valueOf).collect(Collectors.joining(","));
            //2.相同优惠券,金额最大
            CouponDiscountDTO old = moreDiscountMap.get(ids);
            //当前优惠金额小于旧优惠金额
            if (old != null && dto.getDiscountAmount()<=old.getDiscountAmount())continue;
            //3.相同金额，用券数最少
            old = lessCouponMap.get(dto.getDiscountAmount());
            int newSize = dto.getIds().size();
            //金额相同时，当前用券数量大于旧用券数量
            if (old != null && newSize>=old.getIds().size())continue;
            //更新两个map
            moreDiscountMap.put(ids,dto);
            lessCouponMap.put(dto.getDiscountAmount(),dto);
        }
        //求两个map的交集
        List<CouponDiscountDTO> res = CollUtils
                .intersection(moreDiscountMap.values(), lessCouponMap.values())
                .stream()
                .sorted(Comparator.comparing(CouponDiscountDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());
        return res;
    }

    private CouponDiscountDTO calculateSolutionDiscount(Map<Coupon, List<OrderCourseDTO>> map, List<OrderCourseDTO> orderCourses, List<Coupon> solution) {
        //1.创建方案结果DTO对象。
        CouponDiscountDTO dto = new CouponDiscountDTO();
        //2.初始化商品ID和商品折扣明细的映射，初始折扣明细全都设置为0。
        Map<Long, Integer> discountMap = orderCourses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, a -> 0));
        //3.计算该方案的折扣明细。
        //3.1 循环方案中的优惠券。
        for (Coupon coupon : solution) {
            //3.2 取出该优惠券对应的可用课程。
            List<OrderCourseDTO> dtos = map.get(coupon);
            //3.3 计算可用课程的总金额（商品价格 - 该商品的折扣明细）。
            int totalAmount = dtos.stream().mapToInt(a -> a.getPrice() - discountMap.get(coupon)).sum();
            //3.4 判断优惠券是否可用。
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if(!discount.canUse(totalAmount,coupon))continue;
            //3.5 计算该优惠券使用后的折扣值。
            int discountAmount = discount.calculateDiscount(totalAmount, coupon);
            //3.6 更新商品的折扣明细（更新商品ID对应的商品折扣明细）。
            caculateDetailDiscount(discountMap,dtos,discountAmount,totalAmount);
            //3.7 累加每一个优惠券的优惠金额，赋值给方案结果DTO对象。
            dto.getIds().add(coupon.getId());
            dto.getRules().add(discount.getRule(coupon));
            dto.setDiscountAmount(dto.getDiscountAmount()+discountAmount);
        }
        return dto;
    }

    private void caculateDetailDiscount(Map<Long, Integer> discountMap,List<OrderCourseDTO> dtos, int discountAmount, int totalAmount) {
        //目的:更新商品ID对应的商品折扣明细
        int time = 0;
        int remainDiscount = discountAmount;
        for (OrderCourseDTO c : dtos) {
            time++;
            int discount = 0;
            if (time==dtos.size()){
                //说明是最后一个课程;
                discount = remainDiscount;
            }else {
                //不是最后一个课程
                discount = c.getPrice()*discountAmount/totalAmount;
                remainDiscount-=discount;
            }
            discountMap.put(c.getId(),discountMap.get(c.getId())+discount);
        }
    }

    /**
     * 细筛出满足限定条件的优惠券对应的课程信息
     * @param coupons
     * @param orderCourses
     * @return
     */
    private Map<Coupon, List<OrderCourseDTO>> findAviailable(List<Coupon> coupons, List<OrderCourseDTO> orderCourses) {
        //1.遍历初筛后的coupons
        Map<Coupon, List<OrderCourseDTO>> map = new HashMap<>();
        for (Coupon coupon : coupons) {
            //2.优惠券是否限定范围
            List<OrderCourseDTO> orderCourseDTOS = new ArrayList<>(orderCourses);
            if(coupon.getSpecific()){
                //2.1.查询coupon_scope 找出 限定的范围 条件coupon_id
                List<CouponScope> couponScopes = couponScopeService
                        .lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();
                //2.3.封装为课程id的范围集合
                List<Long> bizIds = couponScopes.stream().map(CouponScope::getBizId).collect(Collectors.toList());
                //2.4.遍历orderCourses找出符合范围的课程
                orderCourseDTOS = orderCourses.stream().filter(orderCourseDTO -> bizIds.contains(orderCourseDTO.getId())).collect(Collectors.toList());

            }
            //说明当前优惠券限定了范围 但没有满足范围的课程 所以该券不满足使用条件
            if (CollUtils.isEmpty(orderCourseDTOS)){
                continue;
            }
            //3.符合范围的课程的总金额 是否达到门槛值
            int totalPrice = orderCourseDTOS.stream().mapToInt(OrderCourseDTO::getPrice).sum();
            boolean flag = DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalPrice, coupon);
            //4.判断优惠券可用,加入map中
            if (flag){
                map.put(coupon,orderCourseDTOS);
            }
        }
        return map;
    }


    private void generateUserCouPon(Long userId, Coupon coupon) {
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setCouponId(coupon.getId());
        userCoupon.setUserId(userId);
        //说明未设置有效期  只设置了有效天数
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        if (termBeginTime ==null&& termEndTime ==null){
            LocalDateTime now = LocalDateTime.now();
            termBeginTime = now;
            termEndTime = now.plusDays(coupon.getTermDays());
        }
        userCoupon.setTermBeginTime(termBeginTime);
        userCoupon.setTermEndTime(termEndTime);
        this.save(userCoupon);
    }
}
